#include "Application/WindowManager.hpp"

//////////////////////////////////////////////////////////////////////////
#include <chrono>
#include <thread>

#include <SFML/OpenGL.hpp>


#include <Rocket/Core.h>
#include <Rocket/Controls.h>
#include <Rocket/Debugger.h>
#include <RocketLua/RocketLua.hpp>

#include <UI/UI.hpp>
#include <UI/Document.hpp>

#include <Application/WindowedApp.hpp>


//////////////////////////////////////////////////////////////////////////
Application::WindowManager*	Application::WindowManager::mpInstance	=	nullptr;


//////////////////////////////////////////////////////////////////////////
Application::WindowManager::WindowManager( WindowedApp* app, Core::Script& script )
:	mpMainWindow( nullptr )
	DEVBUILD_INIT( mRocketDebugger, "AppWindow/RocketDebugger", false )
,	mpSystemInterface( new RocketSystemInterface( app->Clock() ) )
,	mScript( script )
,	mClock( app->Clock() )
{
	assert( mpInstance==nullptr );
	mpInstance	=	this;
}

Application::WindowManager::~WindowManager()
{
	assert( mpInstance==this );
	mpInstance	=	nullptr;
}


//////////////////////////////////////////////////////////////////////////
void Application::WindowManager::PushPostCall( PostCall_t pc )
{
	mPostCalls.push_back( pc );
}



bool Application::WindowManager::Initialize()
{
	// TODO: Will probably want a lua interpreter per window, will figure out how to 
	// do that later.  Maybe per-context?
	Rocket::Core::Lua::Plugin::Initialize( mScript );

	Rocket::Core::SetFileInterface( &mFileInterface );
	Rocket::Core::SetSystemInterface( mpSystemInterface );
	Rocket::Core::Initialise();

	_CreateMainWindow();

	DEVBUILD( TwInit( TW_OPENGL, NULL ) );
	DEVBUILD( _MakeDebugPanel() );

	return _InitScript();
}


bool Application::WindowManager::Shutdown()
{
	// Shutdown all the debug panels.
	DEVBUILD(
		for( auto it : mPanels )
		{
			delete it;
		}
	);

	_CloseMainWindow();
	TwTerminate();
	Rocket::Core::Shutdown();

	return true;
}


bool Application::WindowManager::BeginDraw()
{
	if( mpMainWindow && mpMainWindow->isOpen() )
	{
		mpMainWindow->setActive();
		glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
		glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

		return true;
	}
	return false;
}


bool Application::WindowManager::Update()
{
	if( mpMainWindow )
	{
		// Update the window.
		// Note: This division is intended to eventually allow
		// multi monitor support.
		if( !mpMainWindow->Update() )
		{
			_CloseMainWindow();
		}
	}

	return mpMainWindow!=nullptr;
}


void Application::WindowManager::RunPostCalls()
{
	for( auto& pc : mPostCalls )
	{
		pc();
	}
	mPostCalls.clear();
}


void Application::WindowManager::EndDraw()
{
	if( mpMainWindow )
	{
		DEVBUILD(
			TwWindowSize( mpMainWindow->getSize().x, mpMainWindow->getSize().y );
			TwDraw();
		);

		mpMainWindow->display();
	}
}


//////////////////////////////////////////////////////////////////////////
bool Application::WindowManager::LoadFont( const std::string& name )
{
	return Rocket::Core::FontDatabase::LoadFontFace( name.c_str() );
}


bool Application::WindowManager::LoadFonts( const StringVector_t& names )
{
	bool	result	=	true;
	for( const auto& it : names )
	{
		if( LoadFont( it ) )
			continue;
		result	=	false;
	}

	return result;
}


Rocket::Core::ElementDocument* Application::WindowManager::LoadDocument( const std::string& name )
{
	if( mpMainWindow )
	{
		return mpMainWindow->LoadDocument( name );
	}

	return nullptr;
}


//////////////////////////////////////////////////////////////////////////
bool Application::WindowManager::_CreateMainWindow()
{
	mpMainWindow	=	GameWindow::Create(
		this,
		sf::VideoMode( 800, 600, 32 ),
		"XO",
		sf::Style::Default,
		sf::ContextSettings( 32 )
		);
	return mpMainWindow!=nullptr;
}


void Application::WindowManager::_CloseMainWindow()
{
	if( mpMainWindow )
	{
		if( mpMainWindow->isOpen() )
			mpMainWindow->close();
		delete mpMainWindow;
		mpMainWindow	=	nullptr;
	}
}

#ifdef ENABLE_DEVBUILD
void Application::WindowManager::ToggleDebugPanel()
{
	mPanels[ 0 ]->Toggle();
}


void Application::WindowManager::ToggleRocketDebugger()
{
	mRocketDebugger	=	mRocketDebugger ? false : true;
}


bool Application::WindowManager::RocketDebuggerShow() const
{
	return mRocketDebugger;
}


void Application::WindowManager::RegisterPanel( TweakUtils::Panel* panel )
{
	mPanels.push_back( panel );
	mPanels[ 0 ]->Button( panel->Name(), [=]{panel->Toggle();} );

	// Recreate the panel if needed.
	if( mPanels[ 0 ]->Visible() )
	{
		mPanels[ 0 ]->Hide();
		mPanels[ 0 ]->Show();
	}
}


//////////////////////////////////////////////////////////////////////////
void Application::WindowManager::_MakeDebugPanel()
{
	_GetVideoModes();

	LOG( INFO ) << "Initializing debug panel.";
	mPanels.push_back( new TweakUtils::Panel(
		TweakUtils::Panel::Create( "Debug Panel" )
		.LightText()
		.Variable< int32_t >( "Left", [=]{ return mpMainWindow->Position().X(); }, nullptr ).End()
		.Variable< int32_t >( "Top", [=]{ return mpMainWindow->Position().Y(); }, nullptr ).End()
		.Variable< int32_t >( "Width", [=]{ return mpMainWindow->Size().X(); }, nullptr ).End()
		.Variable< int32_t >( "Height", [=]{ return mpMainWindow->Size().Y(); }, nullptr ).End()
		.Variable< bool >( "Focused", [=]{ return mpMainWindow->Focused(); }, nullptr ).End()

#ifdef OS_WINDOWS
		.Variable< bool >( "Maximized", [=]{ return mpMainWindow->Maximized(); }, nullptr ).End()
#endif
		.Separator( "Fullscreen Settings" ).End()
//		.Variable( "Fullscreen", &mFullscreen ).End()
//		.Enumeration( "Fullscreen Mode", mFSModesEnum, &mFullscreenMode ).End()
		.Separator( "Debug Displays" ).End()
		.Button( "Toggle Rocket Debugger", [&]{ToggleRocketDebugger();} ).End()
		) );

	// Add the UI debugger panel.
	RegisterPanel( new TweakUtils::Panel(
		TweakUtils::Panel::Create( "UI Debugger" )
		.LightText()
//		.Variable( "Over UI Element", &sOverUI ).End()
//		.Variable( "Element ID", &sElementId ).End()
		) );

	static float		sNow			=	0.0f;
	static float		sDeltaTime		=	0.0f;
	static float		sScale			=	0.0f;
	static float		sStepRate		=	0.0f;
	static float		sMaxStepRate	=	0.0f;
	static ClockState	sState			=	mClock.State();

	RegisterPanel( new TweakUtils::Panel(
			TweakUtils::Panel::Create( "Game Time" )
			.LightText()
			.Variable( "Now", &sNow ).End()
			.Variable( "Delta", &sDeltaTime ).End()
			.Variable( "Scale", &sScale ).Callback( [&]{ LOG( INFO ) << "Scale changed."; } ).End()
			.Variable( "Step Rate", &sStepRate ).End()
			.Variable( "Max Step Rate", &sMaxStepRate ).End()
			.Button( "Play", [&]{ mClock.State( ClockState::kRunning ); } ).End()
			.Button( "Pause", [&]{ mClock.State( ClockState::kPaused ); } ).End()
			.Button( "Half Time", [&]{ mClock.Scale( mClock.Scale()/2.0f ); } ).End()
			.Button( "Double Time", [&]{ mClock.Scale( mClock.Scale()*2.0f ); } ).End()
			.Callback(
				[&]{
					sNow		=	mClock.ElapsedTime();
					sDeltaTime	=	mClock.DeltaTime();
					sScale		=	mClock.Scale();
					sState		=	mClock.State();
					sStepRate	=	mClock.StepRate();
					sMaxStepRate=	mClock.MaxStep();
				}
			)
		) );
}
#endif


void Application::WindowManager::_GetVideoModes()
{
	// Get all fullscreen video modes.
	const std::vector< sf::VideoMode >&	fsModes		=	sf::VideoMode::getFullscreenModes();

#ifdef ENABLE_DEVBUILD
	// Make an enum list of all modes.
	TweakUtils::Panel::EnumBuilder		enumBuilder	=	TweakUtils::Panel::EnumType( "VideoModes" );
	for( auto& it : fsModes )
	{
		enumBuilder.Entry( std::to_string( it.width ) + "x" + std::to_string( it.height ) + ":" + std::to_string( it.bitsPerPixel ) );
	}

	// Convert the builder to the type.
	mFSModesEnum	=	enumBuilder;
#endif

	// Double check the fullscreen mode is in the valid range.
//	if( mFullscreenMode>=(int32_t)fsModes.size() )
//		mFullscreenMode	=	0;
}


//////////////////////////////////////////////////////////////////////////
// Lua integration.
#include <LuaBridge/LuaBridge.h>

using namespace luabridge;


struct sfVideoModeHelper
{
	static int		GetFullscreenModes( lua_State* state )
	{
		static std::vector< sf::VideoMode >	vmodes;
		/*
		vmodes	=	sf::VideoMode::getFullscreenModes();
		LuaRef	vmodeRef( state, vmodes );
		vmodeRef.push( state );
		*/

		return 0;
	}
};


bool Application::WindowManager::_InitScript()
{
	static int	kRunning	=	int( ClockState::kRunning );
	static int	kPaused		=	int( ClockState::kPaused );
	static int	kStepping	=	int( ClockState::kStepping );

	getGlobalNamespace( mScript )
		.beginClass< sf::VideoMode >( "VideoMode" )
			.addConstructor< void (*)( void ) >()
			.addData( "Width", &sf::VideoMode::width )
			.addData( "Height", &sf::VideoMode::height )
			.addData( "BPP", &sf::VideoMode::bitsPerPixel )
			.addStaticFunction( "GetDesktopMode", &sf::VideoMode::getDesktopMode )
			.addStaticFunction( "GetFullscreenModes", &sfVideoModeHelper::GetFullscreenModes )
			.addFunction( "IsValid", &sf::VideoMode::isValid )
		.endClass();

	getGlobalNamespace( mScript )
		.beginClass< Application::Clock >( "Clock" )
			.addStaticData( "Running", &kRunning )
			.addStaticData( "Paused", &kPaused )
			.addStaticData( "Stepping", &kStepping )
			.addProperty( "State", &Clock::State, &Clock::State )
			.addFunction( "DeltaTime", &Clock::DeltaTime )
			.addFunction( "ElapsedTime", &Clock::ElapsedTime )
		.endClass();

	static int	kNone	=	Rocket::Core::ElementDocument::NONE;
	static int	kFocus	=	Rocket::Core::ElementDocument::FOCUS;
	static int	kModal	=	Rocket::Core::ElementDocument::MODAL;

	getGlobalNamespace( mScript )
		.beginClass< WindowManager >( "WindowManager" )
			.addStaticProperty( "Instance", &WindowManager::Instance )
			.addFunction( "VideoModeCount", &WindowManager::VideoModeCount )
			.addFunction( "LoadFont", &WindowManager::LoadFont )
			.addFunction( "LoadDocument", &WindowManager::LoadDocument )
			.addStaticData( "None", &kNone, false )
			.addStaticData( "Focus", &kFocus, false )
			.addStaticData( "Modal", &kModal, false )
			.addProperty( "Clock", &WindowManager::Clock )
		.endClass();

	return true;
}
